--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 2e81996719d25d884c4192ca24aefc8b8332d677
Parents : eff2edf
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-23T04:54:46-05:00
feat(tests): add property-based tests for URL normalization and interface config parsing to improve robustness
Changes
2 files changed, 73 insertions(+), 0 deletions(-)
Diff
diff --git a/tests/backend/test_http_url_guard_hypothesis.py b/tests/backend/test_http_url_guard_hypothesis.py
new file mode 100644
index 00000000..1bd03f18
--- /dev/null
+++ b/tests/backend/test_http_url_guard_hypothesis.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Property tests for outbound loopback URL normalization."""
+
+from urllib.parse import urlparse
+
+import pytest
+from hypothesis import given, settings
+from hypothesis import strategies as st
+
+from meshchatx.src.backend.http_url_guard import (
+ UnsafeOutboundUrlError,
+ normalize_loopback_http_service_base,
+)
+
+
+@settings(max_examples=200, deadline=None)
+@given(s=st.text(max_size=4000))
+def test_normalize_loopback_only_raises_safe_error_or_returns_origin(s):
+ try:
+ out = normalize_loopback_http_service_base(s)
+ except UnsafeOutboundUrlError:
+ return
+ except Exception as e:
+ raise AssertionError(f"unexpected exception: {type(e).__name__}: {e}") from e
+
+ assert isinstance(out, str)
+ parsed = urlparse(out)
+ assert parsed.scheme in ("http", "https")
+ assert parsed.netloc
+ assert "@" not in parsed.netloc
+ host = (parsed.hostname or "").lower().strip("[]")
+ assert host in ("127.0.0.1", "localhost", "::1")
+ assert parsed.path in ("", "/")
+ assert parsed.query == ""
+ assert parsed.fragment == ""
+
+
+@settings(max_examples=80, deadline=None)
+@given(s=st.one_of(st.none(), st.binary(min_size=0, max_size=256)))
+def test_normalize_loopback_rejects_non_string(s):
+ with pytest.raises(UnsafeOutboundUrlError):
+ normalize_loopback_http_service_base(s)
diff --git a/tests/backend/test_interface_config_parser_hypothesis.py b/tests/backend/test_interface_config_parser_hypothesis.py
new file mode 100644
index 00000000..1bd50762
--- /dev/null
+++ b/tests/backend/test_interface_config_parser_hypothesis.py
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: 0BSD
+
+"""Property tests for Reticulum-style interface config parsing."""
+
+from hypothesis import given, settings
+from hypothesis import strategies as st
+
+from meshchatx.src.backend.interface_config_parser import InterfaceConfigParser
+
+
+@settings(max_examples=150, deadline=None)
+@given(text=st.text(max_size=8000))
+def test_interface_config_parse_never_raises(text):
+ result = InterfaceConfigParser.parse(text)
+ assert isinstance(result, list)
+ for iface in result:
+ assert isinstance(iface, dict)
+ assert "name" in iface
+ assert isinstance(iface["name"], str)
+
+
+@settings(max_examples=120, deadline=None)
+@given(lines=st.lists(st.text(max_size=400), max_size=120))
+def test_interface_config_parse_joined_lines_never_raises(lines):
+ text = "\n".join(lines)
+ result = InterfaceConfigParser.parse(text)
+ assert isinstance(result, list)
+ for iface in result:
+ assert isinstance(iface, dict)
+ assert "name" in iface
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────